<?php
namespace slinks\common\addendum;

/**
 * Addendum PHP Reflection Annotations
 * http://code.google.com/p/addendum/
 *
 * Copyright (C) 2006-2009 Jan "johno Suchal <johno@jsmf.net>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 **/

class CompositeMatcher {
	protected $matchers = array();
	private $wasConstructed = false;

	public function add($matcher) {
		$this->matchers[] = $matcher;
	}

	public function matches($string, &$value) {
		if(!$this->wasConstructed) {
			$this->build();
			$this->wasConstructed = true;
		}
		return $this->match($string, $value);
	}

	protected function build() {}
}

class ParallelMatcher extends CompositeMatcher {
	protected function match($string, &$value) {
		$maxLength = false;
		$result = null;
		foreach($this->matchers as $matcher) {
			$length = $matcher->matches($string, $subvalue);
			if($maxLength === false || $length > $maxLength) {
				$maxLength = $length;
				$result = $subvalue;
			}
		}
		$value = $this->process($result);
		return $maxLength;
	}

	protected function process($value) {
		return $value;
	}
}

class SerialMatcher extends CompositeMatcher {
	protected function match($string, &$value) {
		$results = array();
		$total_length = 0;
		foreach($this->matchers as $matcher) {
			if(($length = $matcher->matches($string, $result)) === false) return false;
			$total_length += $length;
			$results[] = $result;
			$string = substr($string, $length);
		}
		$value = $this->process($results);
		return $total_length;
	}

	protected function process($results) {
		return implode('', $results);
	}
}

class SimpleSerialMatcher extends SerialMatcher {
	private $return_part_index;

	public function __construct($return_part_index = 0) {
		$this->return_part_index = $return_part_index;
	}

	public function process($parts) {
		return $parts[$this->return_part_index];
	}
}

class RegexMatcher {
	private $regex;

	public function __construct($regex) {
		$this->regex = $regex;
	}

	public function matches($string, &$value) {
		if(preg_match("/^{$this->regex}/", $string, $matches)) {
			$value = $this->process($matches);
			return strlen($matches[0]);
		}
		$value = false;
		return false;
	}

	protected function process($matches) {
		return $matches[0];
	}
}

class AnnotationsMatcher {
	public function matches($string, &$annotations) {
		$annotations = array();
		$annotation_matcher = new AnnotationMatcher;
		while(true) {
			if(preg_match('/\s(?=@)/', $string, $matches, PREG_OFFSET_CAPTURE)) {
				$offset = $matches[0][1] + 1;
				$string = substr($string, $offset);
			}  else {
				return; // no more annotations
			}
			if(($length = $annotation_matcher->matches($string, $data)) !== false) {
				$string = substr($string, $length);
				list($name, $params) = $data;
				$annotations[$name][] = $params;
			}
		}
	}
}

class AnnotationMatcher extends SerialMatcher {
	protected function build() {
		$this->add(new RegexMatcher('@'));
		$this->add(new RegexMatcher('[A-Z][a-zA-Z0-9_]+'));
		$this->add(new AnnotationParametersMatcher);
	}

	protected function process($results) {
		return array($results[1], $results[2]);
	}
}

class ConstantMatcher extends RegexMatcher {
	private $constant;

	public function __construct($regex, $constant) {
		parent::__construct($regex);
		$this->constant = $constant;
	}

	protected function process($matches) {
		return $this->constant;
	}
}

class AnnotationParametersMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new ConstantMatcher('', array()));
		$this->add(new ConstantMatcher('\(\)', array()));
		$params_matcher = new SimpleSerialMatcher(1);
		$params_matcher->add(new RegexMatcher('\(\s*'));
		$params_matcher->add(new AnnotationValuesMatcher);
		$params_matcher->add(new RegexMatcher('\s*\)'));
		$this->add($params_matcher);
	}
}

class AnnotationValuesMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new AnnotationTopValueMatcher);
		$this->add(new AnnotationHashMatcher);
	}
}

class AnnotationTopValueMatcher extends AnnotationValueMatcher {
	protected function process($value) {
		return array('value' => $value);
	}
}

class AnnotationValueMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new ConstantMatcher('true', true));
		$this->add(new ConstantMatcher('false', false));
		$this->add(new ConstantMatcher('TRUE', true));
		$this->add(new ConstantMatcher('FALSE', false));
		$this->add(new AnnotationStringMatcher);
		$this->add(new AnnotationNumberMatcher);
		$this->add(new AnnotationArrayMatcher);
		$this->add(new AnnotationStaticConstantMatcher);
		$this->add(new NestedAnnotationMatcher);
	}
}

class AnnotationKeyMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new RegexMatcher('[a-zA-Z][a-zA-Z0-9_]*'));
		$this->add(new AnnotationStringMatcher);
		$this->add(new AnnotationIntegerMatcher);
	}
}

class AnnotationPairMatcher extends SerialMatcher {
	protected function build() {
		$this->add(new AnnotationKeyMatcher);
		$this->add(new RegexMatcher('\s*=\s*'));
		$this->add(new AnnotationValueMatcher);
	}

	protected function process($parts) {
		return array($parts[0] => $parts[2]);
	}
}

class AnnotationHashMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new AnnotationPairMatcher);
		$this->add(new AnnotationMorePairsMatcher);
	}
}

class AnnotationMorePairsMatcher extends SerialMatcher {
	protected function build() {
		$this->add(new AnnotationPairMatcher);
		$this->add(new RegexMatcher('\s*,\s*'));
		$this->add(new AnnotationHashMatcher);
	}

	protected function match($string, &$value) {
		$result = parent::match($string, $value);
		return $result;
	}

	public function process($parts) {
		return array_merge($parts[0], $parts[2]);
	}
}

class AnnotationArrayMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new ConstantMatcher('{}', array()));
		$values_matcher = new SimpleSerialMatcher(1);
		$values_matcher->add(new RegexMatcher('\s*{\s*'));
		$values_matcher->add(new AnnotationArrayValuesMatcher);
		$values_matcher->add(new RegexMatcher('\s*}\s*'));
		$this->add($values_matcher);
	}
}

class AnnotationArrayValuesMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new AnnotationArrayValueMatcher);
		$this->add(new AnnotationMoreValuesMatcher);
	}
}

class AnnotationMoreValuesMatcher extends SimpleSerialMatcher {
	protected function build() {
		$this->add(new AnnotationArrayValueMatcher);
		$this->add(new RegexMatcher('\s*,\s*'));
		$this->add(new AnnotationArrayValuesMatcher);
	}

	protected function match($string, &$value) {
		$result = parent::match($string, $value);
		return $result;
	}

	public function process($parts) {
		return array_merge($parts[0], $parts[2]);
	}
}

class AnnotationArrayValueMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new AnnotationValueInArrayMatcher);
		$this->add(new AnnotationPairMatcher);
	}
}

class AnnotationValueInArrayMatcher extends AnnotationValueMatcher {
	public function process($value) {
		return array($value);
	}
}

class AnnotationStringMatcher extends ParallelMatcher {
	protected function build() {
		$this->add(new AnnotationSingleQuotedStringMatcher);
		$this->add(new AnnotationDoubleQuotedStringMatcher);
	}
}

class AnnotationNumberMatcher extends RegexMatcher {
	public function __construct() {
		parent::__construct("-?[0-9]*\.?[0-9]*");
	}

	protected function process($matches) {
		$isFloat = strpos($matches[0], '.') !== false;
		return $isFloat ? (float) $matches[0] : (int) $matches[0];
	}
}

class AnnotationIntegerMatcher extends RegexMatcher {
	public function __construct() {
		parent::__construct("-?[0-9]*");
	}

	protected function process($matches) {
		return (int) $matches[0];
	}
}

class AnnotationSingleQuotedStringMatcher extends RegexMatcher {
	public function __construct() {
		parent::__construct("'([^']*)'");
	}

	protected function process($matches) {
		return $matches[1];
	}
}

class AnnotationDoubleQuotedStringMatcher extends RegexMatcher {
	public function __construct() {
		parent::__construct('"([^"]*)"');
	}

	protected function process($matches) {
		return $matches[1];
	}
}

class AnnotationStaticConstantMatcher extends RegexMatcher {
	public function __construct() {
		parent::__construct('(\w+::\w+)');
	}

	protected function process($matches) {
		$name = $matches[1];
		if(!defined($name)) {
			trigger_error("Constant '$name' used in annotation was not defined.");
			return false;
		}
		return constant($name);
	}
}

class NestedAnnotationMatcher extends AnnotationMatcher {
	protected function process($result) {
		$builder = new AnnotationsBuilder;
		return $builder->instantiateAnnotation($result[1], $result[2]);
	}
}

